package balgo.ga;

import java.util.ArrayList;
import java.util.List;

/**
 * AdorynZhao in Toutiao-UESTC.
 * Source Code Created on 2017/12/28.
 */
public abstract class TGeneticAlgorithm {

    private List<TChromosome> population = null;

    private int populationSize = 100;
    private int geneSize = 0;
    private int maxIterNum = 500;
    private double mutationRate = 0.1; // 基因变异的概率
    private int maxMutationNum = 3; // 最大变异步长

    private int generation = 1;

    private double bestScore;
    private double worstScore;
    private double totalScore;
    private double averageScore;

    private double x;
    private double y;
    private int gent;

    public TGeneticAlgorithm(int geneSize) {
        this.geneSize = geneSize;
    }

    public void init() {
        this.population = new ArrayList<>();
        for (int i = 0; i < this.populationSize; i++) {
            TChromosome chrom = new TChromosome(this.geneSize);
            this.population.add(chrom);
        }
        this.calculateScore();
    }

    public abstract double changeX(TChromosome chrom);

    public abstract double calculateY(double x);

    private void setChromosomeScore(TChromosome chrom) {
        if (chrom == null)
            return;

        double x = this.changeX(chrom);
        double y = this.calculateY(x);

        chrom.setScore(y);
    }

    public void calculateScore() {
        this.setChromosomeScore(this.population.get(0));
        this.bestScore = this.population.get(0).getScore();
        this.worstScore = this.population.get(0).getScore();
        this.totalScore = 0;

        for (TChromosome chrom : this.population) {
            this.setChromosomeScore(chrom);
            if (chrom.getScore() > this.bestScore) {
                this.bestScore = chrom.getScore();
                if (this.y < this.bestScore) {
                    this.x = this.changeX(chrom);
                    this.y = this.bestScore;
                    this.gent = this.generation;
                }
            }

            if (chrom.getScore() < this.worstScore) {
                this.worstScore = chrom.getScore();
            }

            this.totalScore += chrom.getScore();
        }

        this.averageScore = this.totalScore / this.populationSize;
        this.averageScore = this.averageScore > this.bestScore ? bestScore : averageScore;
    }

    private TChromosome getParentChromosome() {
        double slice = Math.random() * this.totalScore;
        double sum = 0.0;

        for (TChromosome chrom : this.population) {
            sum += chrom.getScore();
            if (sum > slice && chrom.getScore() >= averageScore) {
                return chrom;
            }
        }

        return null;
    }

    private void evolve() {
        List<TChromosome> childPopulation = new ArrayList<>();

        while (childPopulation.size() < populationSize) {
            TChromosome p1 = getParentChromosome();
            TChromosome p2 = getParentChromosome();
            List<TChromosome> children = TChromosome.genetic(p1, p2);
            if (children != null) {
                for (TChromosome chrom : children) {
                    childPopulation.add(chrom);
                }
            }
        }
        List<TChromosome> t = population;
        population = childPopulation;
        t.clear();
        t = null;
        this.mutation();
        this.calculateScore();
    }

    private void mutation() {
        for (TChromosome chrom : this.population) {
            if (Math.random() <= mutationRate) {
                int mutationNum = (int)(Math.random() * maxMutationNum);
                chrom.mutation(mutationNum);
            }
        }
    }

    public void calculate() {
        // 初始化种群
        this.generation = 1;
        init();
        while(this.generation < maxIterNum) {
            this.evolve();
            this.print();
            this.generation++;
        }
    }

    private void print() {
        System.out.println("--------------------------------");
        System.out.println("the generation is:" + generation);
        System.out.println("the best y is:" + bestScore);
        System.out.println("the worst fitness is:" + worstScore);
        System.out.println("the average fitness is:" + averageScore);
        System.out.println("the total fitness is:" + totalScore);
        System.out.println("geneI:" + this.gent + "\tx:" + x + "\ty:" + y);
    }
}
